/*
Hermes Core: General Purpose Mobile Agent Platform
Copyright (C) 2006 UNICAM - Università degli Studi di Camerino

Group leaders: Flavio Corradini and Emanuela Merelli

Developers: Ezio Bartocci, Diego Bonura, Rosario Culmone,
Davide Lorenzoli, Leonardo Mariani, Francesca Piersigilli, 
Lorenzo Scortichini, Leonardo Vito.

GNU Lesser Public License

This library is free software; you can redistribute it 
and/or modify it under the terms of the GNU Lesser General 
Public License as published by the Free Software Foundation, 
version 2.1 of the License.

This library is distributed in the hope that it will be useful, 
but WITHOUT ANY WARRANTY; without even the implied warranty of 
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
See the GNU Library General Public License for more details.

You should have received a copy of the GNU Library General Public 
License along with this library; if not, write to the
Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.

Emanuela Merelli
Dipartimento di Matematica ed Informatica
Università di Camerino
via Madonna delle Carceri, 62032 Camerino, Italy
E.Mail: emanuela.merelli@unicam.it
*/
package hermesV2.core.net.protocols;

import hermesV2.Envelope;
import hermesV2.PlaceAddress;

import hermesV2.util.HermesLogger;
//import hermesV2.util.ObjectMeter;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import java.net.Socket;

import java.util.Random;

import android.util.Log;



/**
 * Makes possible to handle outputs, it sends messages, agents, to a remote
 * place. It waits for the reply of the remote places and synchronizes the
 * access with the <code>reply</code> class.
 *
 * @author Diego Bonura
 */
public class OutputRequestOpen implements Runnable {
    /** the new socket */
    private Socket socket;

    /** the input stream. */
    private ObjectInputStream input;

    /** the output stream. */
    private ObjectOutputStream output;

    /** Counts the number of socket errors. */
    private int errorCounter;

    /** A generic flag. */
    private boolean errorFlag;

    /** True if the reply from the remote place is ready, otherwise no. */
    private boolean replyReady;

    /** A generic <code>Object</code> that it has to send to the remote place. */
    private Object obj;

    /** The answer of the remote place. */
    private Envelope<?> reply;

    /** A generic object to make possible the lock of the current thread. */
    private Object flag;

    /**
     * Creates a new <code>OutputRequest</code> object.
     *
     * 
     * @param receiverPlaceAddress The remote place address.
     * @param obj The obj sent.
     * @param timeOut The timeOut (millisenconds) of the current connection
     *
     * @throws IOException Socket problems.
     * @throws RuntimeException The thread exception.
     */
    public OutputRequestOpen(PlaceAddress receiverPlaceAddress, Object obj, int timeOut) throws IOException, RuntimeException {
        this.obj = obj;
        this.errorCounter = 0;
        this.errorFlag = false;
        this.replyReady = false;
        this.flag = new Object();

        while ((!errorFlag) && (errorCounter++ < 2)) {
            errorFlag = true;

            try {
            	//Studio performance
            	//long prima=System.currentTimeMillis();
            	socket = new Socket(receiverPlaceAddress.getIp(), receiverPlaceAddress.getPort());
                socket.setSoTimeout(timeOut);
                output = new ObjectOutputStream((socket.getOutputStream()));
                input = new ObjectInputStream(new BufferedInputStream(socket.getInputStream()));
                //long dopo=System.currentTimeMillis();
                //HermesLogger.log(HermesLogger.INFO,"TempoConnessione="+(dopo-prima));
                
            	
            } catch (IOException ioe) {
                try {
                    Thread.sleep(200 + (new Random()).nextInt(200 - 50 + 1) + 50);

                    try {
                        if (output != null) {
                            output.close();
                        }

                        if (input != null) {
                            input.close();
                        }

                        if (socket != null) {
                            socket.close();
                        }
                    } catch (IOException ioe2) {
                        HermesLogger.log(HermesLogger.WARNING, "IOException", ioe2);
                        Log.i("hermes", "IOException", ioe2);
                    }
                } catch (InterruptedException ie) {
                    HermesLogger.log(HermesLogger.WARNING, "InterruptedException", ie);
                    Log.i("hermes", "InterruptedException: "+ ie);
                }

                errorFlag = false;
            }
        }

        if (errorFlag == false) {
            throw new IOException("OutputRequest:Something wrong with the connection to: "+receiverPlaceAddress);
        }

        new Thread(this).start();
    }

    /**
     * After the creation of the socket, the tread can now sends and receives
     * messages
     *
     * @throws RuntimeException The thread exception.
     */
    
    public void run() throws RuntimeException {
    	
        synchronized (flag) {
            try {
            	//Studio performance
            	//long prima=System.currentTimeMillis();
                output.writeObject(obj);
                //long dopo=System.currentTimeMillis();
                //int size = hermesV2.util.ObjectMeter.getObjectSize(obj);
                //HermesLogger.log(HermesLogger.INFO,"TempoTasferimento="+(dopo-prima)+"="+size);
                
                reply = (Envelope<?>) (input.readObject());
                replyReady = true;
            } catch (IOException ioe) {
                replyReady = true;
            } catch (ClassNotFoundException cnfe) {
                HermesLogger.log(HermesLogger.WARNING, "ClassNotFoundException", cnfe);
                Log.i("herms", "ClassNotFoundException"+ cnfe);
            } finally {
                try {
                    output.flush();
                    output.close();
                    input.close();
                    socket.close();
                    flag.notify();
                } catch (IOException ioe) {
                    HermesLogger.log(HermesLogger.WARNING, "IOException", ioe);
                    Log.i("hermes", "IOException"+ ioe);
                }
            }
        }
    }

    /**
     * Returns the reply of the remote place.
     *
     * @return The reply of the remote place.
     */
    public Envelope<?> getReply() {
        synchronized (flag) {
            while (!replyReady) {
                try {
                    flag.wait();
                } catch (InterruptedException e) {
                    HermesLogger.log(HermesLogger.WARNING, "InterruptedException", e);
                    Log.i("hermes", "InterruptedException: "+ e);
                }
            }
        }

        return reply;
    }
}
